﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SpotItComponents;

using Vitruvian.Distribution;
using Vitruvian.Distribution.SyncPatterns;
using Vitruvian.Serialization;
using Vitruvian.Serialization.Xml;
using Vitruvian.Services;

using Vitruvian.Logging;
using Vitruvian.Testing;

namespace SpotItServerAPI
{
    [ContainsTests("API.AccountManager")]
    [DistributionInfo]
    [OptimisticSerialization]
    public class AccountManager : DistributedManager
    {
        #region Private Data Members
        protected SymbolManager symbolManager = null;
        private List<UserAccount> userAccounts = new List<UserAccount>();
        private string userAccountsFile = "UserAcounts.xml";
        #endregion

        #region Constructor
        public AccountManager() : base("Account Manager")
        {
            logger = Logger.GetLogger(typeof(AccountManager));
        }
        #endregion

        #region Public Properties and Methods

        public string UserAccountsFile
        {
            get { return userAccountsFile; }
            set { userAccountsFile = value; }
        }

        [SyncPattern("RPC")]
        public virtual OperationResult RegisterPlayer(string username, string password, string fullName, string email)
        {
            OperationMethod mth = new OperationMethod(InternalRegister);
            return DoOperation(null, mth, SecurityLevel.None, username, password, fullName, email, false, false);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult RegisterDesigner(string username, string password, string fullName, string email)
        {
            OperationMethod mth = new OperationMethod(InternalRegister);
            return DoOperation(null, mth, SecurityLevel.None, username, password, fullName, email, true, false);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult RegisterAdmin(Session currentSession, string username, string password, string fullName, string email)
        {
            OperationMethod mth = new OperationMethod(InternalRegister);
            return DoOperation(currentSession, mth, SecurityLevel.Admin, username, password, fullName, email, true, true);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Login(string username, string password)
        {
            OperationMethod mth = new OperationMethod(InternalLogin);
            return DoOperation(null, mth, SecurityLevel.None, username, password);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult LoginPlayer(string playerName)
        {
            OperationMethod mth = new OperationMethod(InternalLoginPlayer);
            return DoOperation(null, mth, SecurityLevel.None, playerName);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Logout(Session session)
        {
            OperationMethod mth = new OperationMethod(InternalLogout);
            return DoOperation(session, mth, SecurityLevel.Player);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Lookup(Session currentSession, string username)
        {
            OperationMethod mth = new OperationMethod(InternalLookup);
            return DoOperation(currentSession, mth, SecurityLevel.Admin, username);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult AllUsers(Session currentSession)
        {
            OperationMethod mth = new OperationMethod(InternalAllUsers);
            return DoOperation(currentSession, mth, SecurityLevel.Admin);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult Remove(Session currentSession, UserAccount userAccount)
        {
            OperationMethod mth = new OperationMethod(InternalRemove);
            return DoOperation(currentSession, mth, SecurityLevel.Admin, userAccount);
        }

        [SyncPattern("RPC")]
        public virtual OperationResult RemoveAllUserAccounts(Session currentSession)
        {
            OperationMethod mth = new OperationMethod(InternalRemoveAllUsers);
            return DoOperation(currentSession, mth, SecurityLevel.Admin);
        }

        #endregion

        #region Internal Methods for DoOperation
        protected override OperationResult PreCondition()
        {
            OperationResult result = new OperationResult();

            symbolManager = ServiceRegistry.GetPreferredService<SymbolManager>();
            if (symbolManager == null)
                result.ErrorMessage = "Cannot location a Category Manager -- please restart server process";

            return result;
        }

        private OperationResult InternalRegister(object[] opParams)
        {
            OperationResult result;
            Session currentSession = opParams[0] as Session;

            string username = (string) opParams[1];
            string password = (string) opParams[2];
            string fullName = (string) opParams[3];
            string email = (string) opParams[4];
            bool isDesigner = (bool) opParams[5];
            bool isAdmin = (bool) opParams[6];

            Session newSession = null;

            if (!string.IsNullOrEmpty(username) &&
                !string.IsNullOrEmpty(password) &&
                !string.IsNullOrEmpty(fullName))
            {
                logger.DebugFormat("Register user with username={0}, password={1}, fullName={2}, email={3}", username, password, fullName, email);

                UserAccount userAccount = new UserAccount(username, password, fullName, email);
                userAccount.IsDesigner = isDesigner;
                userAccount.IsAdmin = isAdmin;
                newSession = SetupNewAccount(userAccount);
                if (newSession == null)
                    result = new OperationResult("Couldn't create a user account or session");
                else
                {
                    result = new OperationResult();
                    result.Data = newSession;
                }
            }
            else
                result = new OperationResult("Invalid user account settings");

            return result;
        }

        private OperationResult InternalLogin(object[] opParams)
        {
            OperationResult result = new OperationResult();

            logger.Debug("Login");

            string username = (string)opParams[1];
            string password = (string)opParams[2];
            foreach (UserAccount account in userAccounts)
            {
                if (account.Username == username && account.Password == password)
                {
                    logger.Debug("Found the user account, so create session");
                    result.Data = sessionManager.CreateSession(account);
                    break;
                }
            }

            if (result.Data==null)
                result.ErrorMessage = "Cannot find a username with the specified password";

            return result;
        }

        private OperationResult InternalLoginPlayer(object[] opParams)
        {
            OperationResult result = new OperationResult();

            string playerName = (string) opParams[1];
            if (!string.IsNullOrEmpty(playerName))
                result.Data = sessionManager.CreateSession(playerName);
            else
                result.ErrorMessage = "Cannot log in a player without a name";

            return result;
        }

        private OperationResult InternalLogout(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;

            if (!sessionManager.RemoveSession(currentSession))
                result.ErrorMessage = "Cannot logout of the specified session";

            return result;
        }

        private OperationResult InternalLookup(object[] opParams)
        {
            OperationResult result = new OperationResult();

            string username = (string)opParams[1];
            foreach (UserAccount u in userAccounts)
            {
                if (u.Username == username)
                {
                    result.Data = u;
                    break;
                }
            }

            if (result.Data == null)
                result.ErrorMessage = "Cannot find specified user";

            return result;
        }

        private OperationResult InternalAllUsers(object[] opParams)
        {
            OperationResult result = new OperationResult();

            result.Data = userAccounts;

            return result;
        }

        private OperationResult InternalRemove(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;
            UserAccount userAccount = opParams[1] as UserAccount;
            for (int i = 0; i < userAccounts.Count; i++)
            {
                if (userAccounts[i].Username == userAccount.Username)
                {
                    // Before deleting the user, move all of that user's symbols over to the administrator
                    symbolManager.ChangeOwnership(currentSession, userAccounts[i].Id, currentSession.UserInfo.Id);

                    // TODO: move ownership of decks

                    // Remove the user account
                    userAccounts.RemoveAt(i);
                    Save();
                    break;
                }
            }

            return result;
        }

        private OperationResult InternalRemoveAllUsers(object[] opParams)
        {
            OperationResult result = new OperationResult();

            Session currentSession = opParams[0] as Session;

            // Remove all symbol sets
            symbolManager.RemoveAllSymbolSets(currentSession);

            // TODO: Remove all decks

            userAccounts.Clear();
            SetupDefaultAdminAccount();

            return result;
        }



        #endregion

        #region IService Members

        public override void Init()
        {
            Load();
        }

        public override void Cleanup()
        {
            Save();
        }

        #endregion

        #region Private Data Members

        protected override void Load()
        {
            lock (myLock)
            {
                if (!string.IsNullOrEmpty(UserAccountsFile))
                    XmlFramework.Deserialize(UserAccountsFile, userAccounts);                
            }

            if (!DefaultAdminAccountExists())
                SetupDefaultAdminAccount();
        }

        protected override void Save()
        {
            lock (myLock)
            {
                if (!string.IsNullOrEmpty(UserAccountsFile))
                    XmlFramework.Serialize(UserAccountsFile, userAccounts);
            }
        }

        private bool DefaultAdminAccountExists()
        {
            bool result = false;
            lock (myLock)
            {
                foreach (UserAccount u in userAccounts)
                    if (u.Username=="admin")
                    {
                        result = true;
                        break;
                    }
            }
            return result;
        }

        private void SetupDefaultAdminAccount()
        {
            UserAccount defaultAdminAccount = new UserAccount("admin", "admin", "Default Admin Account", "");
            lock (myLock)
            {
                defaultAdminAccount.IsDesigner = true;
                defaultAdminAccount.IsAdmin = true;
                userAccounts.Add(defaultAdminAccount);
                Save();
            }
        }

        private Session SetupNewAccount(UserAccount userAccount)
        {
            Session result = null;
            lock (myLock)
            {
                userAccounts.Add(userAccount);
                Save();
            }

            if (sessionManager!=null)
                result = sessionManager.CreateSession(userAccount);

            return result;
        }

        #endregion

    }
}
